#!/usr/bin/python
# -*- coding: utf8 -*

from nodos_gramatica import SimboloNoTerminal,GrammarError
from generador_codigo import GeneradorDeCodigo

from sets import Set


class GeneratingParserError(Exception):
    def __init__(self,e,nt):
        self.e = e
        self.nt = nt
    
    def __str__(self):
        return "Durante la generación del parser para el no terminal %s \n" % self.nt + \
               "se detectó un conflicto en el nodo '%s': \n" % self.e.nodo + \
               self.e.mensaje
                    


################################
# Tablas para algoritmos       #
################################

from copy import deepcopy

class Tabla:
    def __init__(self, claves, default):
        self.modificada = True
        self.t = {}
        for c in claves:
            self.t[c] = deepcopy(default)

    def __str__(self):
        b = []
        b.append("%s (%s elementos)" % (self.__class__.__name__,len(self.t)))
        for k in self.t:
            b.append("  %s: %s" % (k, self.t[k]))
        return '\n'.join(b)

    def __setitem__(self, k, v):
        '''Definición de asignación a un subíndice'''
        if not k in self.t:
            self.t[k] = v
            self.modificada = True
        else:
            v_original = self.t[k]
            self.t[k] = v

            if v_original != v:
                self.modificada = True

    def __getitem__(self, k):
        '''Definición de acceso a un subíndice'''
        return self.t[k]

    def __contains__(self, k):
        '''Deficion de la operación "in" para una clave'''
        return k in self.t



################################
# Clase ParserELL1             #
################################

class ParserELL1:
    def __init__(self, gramatica):
        self.g = gramatica

        # Tablas de memoización
        self.tablaAnulables = None
        self.tablaPrimeros = None
        self.tablaSiguientes = None
        self.tablaDirectores = None
        self.tablaInutiles = None
        
        self._calcularInutiles()               
        self._calcularDirectores()


    def __repr__(self):
        return ''.join(['ParserELL1 object at %s' % hex(id(self)),
                      '\nAnulables: %s\n' % str(self.ta),
                      '\nPrimeros: %s\n' % str(self.tp),
                      '\nSiguientes: %s\n' % str(self.ts),
                      '\nDirectores: %s\n' % str(self.td),
                      '\nInutiles: %s\n'  % str(self.ti),
                      ])


    def _calcularAnulables(self):
        '''Computa la tabla de anulables y la carga con los datos'''
        self.tablaAnulables = Tabla(self.g.noTerminales, False)
        
        # Alias
        ta = self.tablaAnulables
        self.ta = self.tablaAnulables

        # Recalculo los anulables hasta que no haya cambios
        while(ta.modificada):
            ta.modificada = False
            for nt in self.g.noTerminales:
                ta[nt] = self.g.producciones[nt].anulable(self)


    def _calcularPrimeros(self):
        '''Computa la tabla de primeros y la carga con los datos'''
        self._calcularAnulables()
        self.tablaPrimeros = Tabla(self.g.noTerminales, Set())

        # Alias
        ta = self.tablaAnulables
        tp = self.tablaPrimeros
        self.tp = self.tablaPrimeros

        # Recalculo los primeros hasta que no haya cambios
        while(tp.modificada):
            tp.modificada = False
            for nt in self.g.noTerminales:
                tp[nt] = tp[nt].union(self.g.producciones[nt].primeros(self))


    def _calcularInutiles(self):
        '''Computa la tabla de inútiles y la carga con los datos'''  
        self.tablaInutiles = Tabla(self.g.noTerminales,True)

        # Alias
        ti = self.tablaInutiles
        self.ti = self.tablaInutiles

        # Recalculo los inutiles hasta que no haya cambios
        while(ti.modificada):
            ti.modificada = False
            for nt in self.g.noTerminales:
                ti[nt] = self.g.producciones[nt].inutil(self)
        
        
    def _calcularSiguientes(self):
        '''Computa la tabla de siguientes y la carga con los datos'''  
        self._calcularPrimeros()
        self.tablaSiguientes = Tabla(self.g.nodos(), Set())

        # Alias
        g = self.g
        ta = self.tablaAnulables
        tp = self.tablaPrimeros
        ts = self.tablaSiguientes
        self.ts = self.tablaSiguientes
        
        # Inicializo con el símbolo de terminación y lo 
        # propago por la tabla
        terminador = Set(['$'])
        ts[SimboloNoTerminal(g.simboloDistinguido)] = terminador
        g.producciones[g.simboloDistinguido].agregar_siguientes(self, terminador)
        
        # Recalculo los siguientes hasta que no haya cambios
        while(ts.modificada):
            ts.modificada = False
            for nt in g.noTerminales:
                siguientes_nt = ts[SimboloNoTerminal(nt)]
                g.producciones[nt].agregar_siguientes(self, siguientes_nt)
                SimboloNoTerminal(nt).agregar_siguientes(self, ts[g.producciones[nt]])

    def _calcularDirectores(self):
        '''Computa la tabla de símbolos directores y la carga con los datos'''  
        self._calcularSiguientes()
        self.tablaDirectores = Tabla(self.g.nodos(), None)

        # Alias
        g = self.g
        ta = self.tablaAnulables
        tp = self.tablaPrimeros
        ts = self.tablaSiguientes
        td = self.tablaDirectores
        self.td = self.tablaDirectores
        
        # Calculo los directores
        try:
            for nodo in g.nodos(): 
                if td[nodo] is None:
                    td[nodo] = nodo.directores(self)
        except GrammarError, e:
            nodo = self.g.buscarNTAsociado(e.nodo)
            raise GeneratingParserError(e, nodo)
        

    def generarCodigo(self):
        '''Genera el código del Parser ELL(1) para la gramática asociada a esta instancia'''
        cg = GeneradorDeCodigo()
        for p in self.g.producciones:
            cg.linea("")
            cg.linea("def noterm_%s():" % p)
            cg.indentar()
            self.g.producciones[p].directores(self)
            self.g.producciones[p].generarCodigo(self, cg)
            cg.desindentar()
        cg.linea("")
        cg.linea("try:")
        cg.indentar()
        cg.linea("noterm_%s()" % self.g.simboloDistinguido)
        cg.linea("match('$')")
        cg.linea("print 'OK!'")
        cg.desindentar()
        cg.linea("except ParseError, e:")
        cg.indentar()
        cg.linea("print e")
        cg.desindentar()
        
        return cg.generar_codigo()
        
    def getInutiles(self):
        '''Devuelve el conjunto de nodos inútiles de la gramática asociada a esta instancia'''
        self._calcularInutiles()

        res = Set()
        for nt in self.g.noTerminales:
            if self.ti[nt]:
                res.add(nt)
                
        return res

